#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/smlschemas.h>

#include "misc.hpp"
#include "designM/composite.hpp"
#include "details/conffilexml.hpp"
#include "base64.hpp"
#include "mallocSharedPtr.hpp"

using wheels;
using dm;

bool confFileVerify :: verify( const std::string& xml , const std::string& sch )
{
	bool ret = false;
	xmlDocPtr doc = xmlReadFile(xml.c_str() , nullptr , 0 );    
	if (doc == NULL) {    
		return false;    
	}    
    
	xmlNodePtr root_element = xmlDocGetRootElement( doc );    
	if (root_element == NULL) {    
		xmlFreeDoc(doc);    
		return false;    
	}        

	xmlSchemaParserCtxtPtr schemaParserCtx = xmlSchemaNewParserCtxt( sch );    
	if (schemaParserCtx == NULL) {    
		xmlFreeDoc(doc);    
		return false;    
	}    
    
	xmlSchemaPtr schema = xmlSchemaParse(schemaParserCtx);    
	if (schema == NULL) {    
		xmlSchemaFreeParserCtxt(schemaParserCtx);    
		xmlFreeDoc(doc);    
		return false;    
	}    
    
	xmlSchemaValidCtxtPtr validCtx = xmlSchemaNewValidCtxt(schema);    
	if (validCtx == NULL) {    
		xmlSchemaFreeParserCtxt(schemaParserCtx);    
		xmlFreeDoc(doc);    
		return false;    
	}    
    

	int validationResult = xmlSchemaValidateDoc(validCtx, doc );    

	xmlSchemaFreeValidCtxt(validCtx);  
	xmlSchemaFreeParserCtxt(schemaParserCtx);   

	xmlFreeDoc(doc);   
	return validationResult == 0;   
}

// ****************************************************************************************************
confFileReader :: confFileReader( const std::string& file ):
	pt_file__( nullptr , [=]( xmlDoc * doc ){ if( doc ) {xmlFreeDoc( doc); } } )
{
	xmlDocPtr doc = xmlReadFile( file.c_str() , nullptr , 0 );
	if( !doc ){
		throw std::runtime_error( "" );
	}

	pt_file__.reset( doc );

	if( !xml2dom__() ){
		throw std::runtime_error( "" );
	}
}
confFileReader :: ~confFileReader()
{
}

bool confFileReader :: load( const std::string& file )
{
	xmlDocPtr doc = xmlReadFile( file.c_str() , nullptr , 0 );
	if( !doc ){
		return false;
	}

	pt_file__.reset( doc );

	return !xml2dom__();
}

bool confFileReader :: check_type_name__( const char * type )
{
	bool ret = false;
	switch( hash_( type ) ){
	case "bool"_hash:
	case "int"_hash:
	case "real"_hash:
	case "date"_hash:
	case "datetime"_hash:
	case "string"_hash:
	case "hex"_hash:
	case "base64"_hash:
	case "url"_hash:
	case "ip"_hash:
	case "email"_hash:
		ret = true;
		break;
		
	}
	return ret;
}


bool confFileReader :: check_data_type__( const char * type , const char * data )
{
	bool ret = false;
	switch( hash_( type ) ){
	case "bool"_hash:{
		std::string str( data );
		ret = ( data == "true" || data == "false");
	}break;
	case "int"_hash:{
		std::regex reg( R"(^[-+]?\d+?$)" );
		ret = std::regex_match( data , reg );
	}break;
	case "real"_hash:{
		std::regex reg( R"([-+]?\d+(\.\d+)?([eE][-+]?\d+)?)" );
		ret = std::regex_match( data , reg );
	}break;
	case "date"_hash:{
		std::regex reg(R"((^\d{4}-(0[1-9]|1[0-2])-([12][0-9]|0[1-9]|3[01])$)|(^\d{2}-(0[1-9]|1[0-2])-([12][0-9]|0[1-9]|3[01])$))");
		ret = std::regex_match( data , reg );
	}break;
	case "datetime"_hash:{
		std::regex reg(R"(\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2})"); 
		ret = std::regex_match( data , reg );
	}break;
	case "string"_hash:{
		ret = true;
	}break;
	case "hex"_hash:{
		std::regex reg( "(^([0-9a-fA-F]{2})+$)" );
		ret = std::regex_match( data , reg );
	}break;
	case "base64"_hash:{
		std::regex reg(R"([A-Za-z0-9+/]{4})*={0,2})"); 
		ret = std::regex_match( data , reg );
	}break;
	case "url"_hash:{
		std::regex reg(R"((\w+?://)?([\da-z.-]+)\.([\da-z.-]+)(\/[-\w .%/?]*)*\??([\w ]*=[\w ]*)&?([\w ]*=[\w ]*)*))");
		ret = std::regex_match( data , reg );
	}break;
	case "ip"_hash:{
		std::regex reg(R"(((\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}))"); 
		ret = std::regex_match( data , reg );
	}break;
	case "email"_hash:{
		std::regex reg(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
		ret = std::regex_match( data , reg );
	}break;
		
	}
	return ret;

}

bool confFileReader :: check_data_node__( xmlNodePtr node )
{
	bool ret = true;
	char * type = xmlGetProp( node , (xmlChar*)"type" );
	if( !type ) return false;
	
	char * ctnt = xmlNodeGetContent( node );
	if( !ctnt ) return false;
	
	ret = check_type_name__( type ); // 检查数据类型是否合法
	if( ret ){ // 在数据类型合法的情况下，检查实际的数据是否合法
		ret = check_data_type__( type , ctnt );
	}
	return ret;
}

bool confFileReader :: check_array_node__( xmlNodePtr node )
{
	bool ret = true;
	char * type = xmlGetProp( node , (xmlChar*)"type" );
	if( !type ) return false;
	
	char * ctnt = xmlNodeGetContent( node );
	if( !ctnt ) return false;
	
	ret = check_type_name__( type ); // 检查数据类型是否合法
	if( ret ){ // 在数据类型合法的情况下，检查实际的数据是否合法
		ret = check_data_type__( type , ctnt );
	}
	return ret;
}

bool confFileReader :: check_group_node__( xmlNodePtr node )
{
}
bool confFileReader :: check_enum_node__( xmlNodePtr node )
{
}
bool confFileReader :: check_root_node__( xmlNodePtr node )
{
}
bool confFileReader :: check_array_item__( xmlNodePtr node )
{
}
bool confFileReader :: check_enum_item__( xmlNodePtr node )
{
}

bool confFileReader :: check_node__( xmlNodePtr node )
{
	char* name = node->name;
	if( name == nullptr ){
		return false;
	}

	bool ret = false;
	switch( hash__( name ) ){
	case "DATA"_hash:
		ret = check_data_node__( node );
		break;
	case "ARRAY"_hash:
		ret = check_array_node__( node );
		break;
	case "GROUP"_hash:
		ret = check_group_node__( node );
		break;
	case "ENUM"_hash:
		ret = check_enum_node__( node );
		break;
	case "confFile"_hash:
		ret = check_confFile_node__( node );
		break;
	case "item"_hash:
		ret = check_array_item_item__( node );
		break;
	case "e"_hash:
		ret = check_enum_e_item__( node );
		break;
	default:
		return false;	
	}

	return ret;
}

stDOMItem
confFileReader :: convert_data_node__( const std::string& type , const std::string& name , xmlNodePtr node )
{
	stDOMItem ret;
	const char * content = xmlNodeGetContent(node);

	switch( hash_( type.c_str() ) ){
	case "bool"_hash:{
		bool data = ( std::string( content ) == "true" );
		ret = {name , data };
	}break;
	case "int"_hash:{
		std::stringstream ss;
		ss << content;
		int data = 0;
		ss >> data;
		ret = { name , data };
	}break;
	case "real"_hash:{
		std::stringstream ss;
		ss << content;
		double data = 0.0;
		ss >> data;
		ret = { name , data };
	}break;
	case "date"_hash:
	case "datetime"_hash:
		
		break;
	case "string"_hash:
		ret = {name , std::string( content ) };
		break;
	case "hex"_hash:{
		mallocSharedPtr< uint8_t >  buff;
		std::string str( content );

		ret = { name , buff };
	}break;
	case "base64"_hash:{
		std::string str( content );
		mallocSharedPtr< uint8_t >  buff(str.length());
		size_t len = Base64Decode( str , buff.get() );
		buff.dataLen( len );
		ret = { name , buff };
	}break;
	case "url"_hash:
	case "ip"_hash:{
		ret = {name , std::string( content ) };
	}break;
	}
	return ret;
}

stDOMItem
confFileReader :: convert_array_node__( const std::string& type , const std::string& name , xmlNodePtr node )
{
	stDOMItem ret;
	
	xmlNodePtr p_children = node->children;
	
	switch( hash_( type.c_str() ) ){
	case "bool"_hash:{
		std::vector< bool > data;
		while( p_children ){
			const char * content = xmlNodeGetContent( p_children );
			bool tmp_data = ( std::string( content ) == "true" );
			data.push_back( tmp_data );

			p_children = p_children->next;
		}
		ret = stDOMItem( name , data );
	}break;
	case "int"_hash:{
		std::vector< int > data;
		while( p_children ){
			const char * content = xmlNodeGetContent( p_children );
			std::stringstream ss;
			ss << content;
			int d = 0;
			ss >> d;
			data.push_back( d );

			p_children = p_children->next;
		}
		ret = stDOMItem( name , data );
	}break;
	case "real"_hash:{
		std::vector< double >   data;
		while( p_children ){
			std::stringstream ss;
			const char * content = xmlNodeGetContent( p_children );
			ss << content;
			double d = 0.0;
			ss >> d;
			data.push_back( d );

			p_children = p_children->next;
		}
		ret = stDOMItem( name , data );
	}break;
	case "date"_hash:
	case "datetime"_hash:
		
		break;
	case "string"_hash:{
		std::vector< std::string > data;
		while( p_children ){
			const char * content = xmlNodeGetContent( p_children );
			std::string d( content );

			data.push_back( d );

			p_children = p_children->next;
		}
		ret = stDOMItem( name , data );
	}break;
	case "hex"_hash:{
		std::vector< mallocSharedPtr< uint8_t > > data;
		while( p_children ){
			mallocSharedPtr< uint8_t >  buff;
			const char * content = xmlNodeGetContent( p_children );
			std::string str( content );

			p_children = p_children->next;
		}
		ret = stDOMItem( name , buff );
	}break;
	case "base64"_hash:{
		std::vector< mallocSharedPtr< uint8_t > > data;
		
		while( p_children ){
			const char * content = xmlNodeGetContent( p_children );
			std::string str( content );
			mallocSharedPtr< uint8_t >  buff(str.length());
			size_t len = Base64Decode( str , buff.get() );
			buff.dataLen( len );

			data.push_back( buff );
			p_children = p_children->next;
		}
		ret = stDOMItem( name , buff );
	}break;
	case "url"_hash:
	case "ip"_hash:{
		std::vector< std::string >  data;
		while( p_children ){
			const char * content = xmlNodeGetContent( p_children );
			std::string str( content );

			data.push_back( str );
			p_children = p_children->next;
		}
		ret = stDOMItem( name , data );
	}break;
	}
	return ret;
}

stDOMItem
confFileReader :: convert_group_node__( const std::string& type , const std::string& name , xmlNodePtr node )
{
}

stDOMItem
confFileReader :: convert_enum_node__( const std::string& type , const std::string& name , xmlNodePtr node )
{
}

stDOMItem confFileReader :: convert_confFile_node__( const std::string& type , const std::string& name , xmlNodePtr node )
{
}

stDOMItem confFileReader :: convert_array_item_node__( const std::string& type , const std::string& name , xmlNodePtr node )
{
}

stDOMItem confFileReader :: convert_enum_e_node__( const std::string& type , const std::string& name , xmlNodePtr node )
{
}

stDOMItem
confFileReader :: convert_data__(const std::string& type , const std::string& n ,  xmlNodePtr node )
{
	stDOMItem ret;
	const char * name = xmlGetProp( node , (const xmlChar *)"name" );
	switch( hash__( n.c_str() ) ){
	case "DATA"_hash:
		ret = convert_data_node__( type , name , node );
		break;
	case "ARRAY"_hash:
		ret = convert_array_node__( type , name , node );
		break;
	case "GROUP"_hash:
		ret = convert_group_node__( type , name , node );
		break;
	case "ENUM"_hash:
		ret = convert_enum_node__( type , name , node );
		break;
	case "confFile"_hash:
		ret = convert_confFile_node__( type , name , node );
		break;
	case "item"_hash:
		ret = convert_array_item_node__( type , name , node );
		break;
	case "e"_hash:
		ret = convert_enum_e_node__( type , name , node );
		break;
	default:
		return false;	
	}
	return ret;
}

std::shared_ptr< composite< stDOMItem > >
confFileReader :: todom__( std::shared_ptr< composite< stDOMItem > > parent , xmlNodePtr node )
{
	std::shared_ptr< composite< stDOMItem > > ret;
	if( node == nullptr ) return ret;
	try{
		if( check_node__( node ) == true ){
			std::string type , name;
			const char * data = node->name;
			name = data;
			data = xmlGetProp( node , (const xmlChar *)"type" );
			type = data;
			
			stDOMItem item = convert_data__( type , name , node );
			ret = std::make_shared< composite< stDOMItem > >( item );
			
			parent->add( ret );
		}
	}catch( std::bad_alloc& e ){
		ERROR_MSG( e.what() );
	}
	return ret;
}

bool confFileReader :: xml2dom__()
{
	bool ret = true;

	xmlNodePtr current = xmlDocGetRootElement( pt_file__.get() );
	std::shared_ptr< composite< stDOMItem > > domCurrent = pt_dom__;
	std::shared_ptr< dm::composite< stDOMItem > stack;
	
	while (current != NULL) {
		// 添加数据节点内容
		domCurrent = todom__( domCurrent , current );
		if( !ret ){
			break;
		}
		// 遍历子节点
		if (current->children != NULL) {
			stack.push( domCurrent );
			current = current->children;
		       
		}else if(current->next != NULL) {
			current = current->next;
		}else {
			xmlNodePtr parent = current->parent;
			while (parent != NULL && parent->next == NULL) {
				parent = parent->parent;
				pt_stack__->pop();
			}
			
			if (parent != NULL) {
				current = parent->next;
			}else {
				current = NULL;
			}
		}
	}
	return ret;
}

dom_t confFileReader :: dom()
{
	return pt_dom__;
}

